package net.cyndeline.scalarlib.rldungeon.dgs.strategy.help

import scalax.collection.GraphEdge.{ExtendedKey, NodeProduct, EdgeCopy, UnDiEdge}
import scalax.collection.GraphPredef.OuterEdge
import scala.Option
import net.cyndeline.scalarlib.rlgraph.util.EdgeCopyFactory
import net.cyndeline.scalarlib.rldungeon.dgs.graph.Room

/**
 * Since Scala's graph library requires that its implemented edges only specify a single generic parameter, this outer
 * class wraps the inner edge, while also supplying the parameter used for the original targets of the inner edge.
 *
 * @tparam X The type of object that the inner edge stores as its original targets.
 */
class CollapsedEdge[X <: Room[X]] {

  /**
   * Constructs a new inner edge.
   *
   * This method is used by factories, users should refer to the CollapsedEdge object.
   */
  def makeEdge[N](from: N, to: N, origFrom: Option[X], origTo: Option[X]) =
    new Inner[N](NodeProduct(from, to), origFrom, origTo) with OuterEdge[N, CollapsedEdge[X]#Inner]

  /**
   * Connects vertices in a graph that folds cycles into super-nodes.
   *
   * Usage: implicit def edge2CollapsedEdgeAssoc[A <: CollapsedNode](e: UnDiEdge[A]) = new CollapsedEdgeAssoc[A](e)
   *
   * @constructor Creates the inner vertex-neutral edge object.
   * @param originalFrom If this edge connects to a super-node in position _1, this value is the vertex in the original
   *                     graph that the vertex inside the super-node originally represented. Example: The edge 1~2.
   *                     Vertex 1 and 2 gets wrapped into collapsed nodes, and the collapsed node that represents 1 later
   *                     is folded into a super-node. This variable would then store the vertex 1.
   * @param originalTo Same as originalFrom, but for _2.
   */
  class Inner[N](nodes: Product, val originalFrom: Option[X], val originalTo: Option[X]) extends UnDiEdge[N](nodes)
    //with ExtendedKey[N]
    with EdgeCopy[Inner]
    with OuterEdge[N,Inner]{

    //def keyAttributes: Seq[Any] = Seq(originalFrom, originalTo)

    override def copy[T](newNodes: Product) = new Inner[T](newNodes, originalFrom, originalTo)

    override def toString(): String = "\n" + this._1 + "~" + this._2 +
      (if (originalFrom.isDefined) " OF:" + originalFrom.get else "") +
      (if (originalTo.isDefined) " OT:" + originalTo.get else "")

    /* Should be ok to only use nodes here, without extended key the original nodes won't be a part of equals,
     * and there won't be two edges between the same node pair anyway.
     */
    override def hashCode(): Int = this._1.## ^ this._2.## << 32
  }

  /**
   * Used when replacing vertices connected by this type of edge.
   * @constructor Constructs a new factory for copying collapsed edges.
   */
  final class CollapsedEdgeFactory[T <: Room[T]] extends EdgeCopyFactory[CollapsedNode[T], CollapsedEdge[T]#Inner] {
    def copyEdge(edge: CollapsedEdge[T]#Inner[CollapsedNode[T]],
                 a: CollapsedNode[T],
                 b: CollapsedNode[T]): CollapsedEdge[T]#Inner[CollapsedNode[T]] with OuterEdge[CollapsedNode[T], CollapsedEdge[T]#Inner] =
      CollapsedEdge(a, b, edge.originalFrom, edge.originalTo)
  }

  /**
   * Constructs a new collapsed edge factory.
   * @return An implemented EdgeCopyFactory that produces inner collapsed edges connected using collapsed nodes.
   */
  def newFactory: CollapsedEdgeFactory[X] = new CollapsedEdgeFactory[X]()

}

final class CollapsedEdgeAssoc[T <: Room[T], A <: CollapsedNode[T]](val e: UnDiEdge[A]) {
  @inline def emptyEdge() = CollapsedEdge(e._1, e._2, None, None)
  @inline def setOriginalTargets (originalFrom: Option[T], originalTo: Option[T]) =
    CollapsedEdge(e._1, e._2, originalFrom, originalTo)
}

/**
 * Object used for quick edge creation.
 */
object CollapsedEdge {

  def apply[T <: Room[T]](from: CollapsedNode[T], to: CollapsedNode[T]) = new CollapsedEdge[T]().makeEdge(from, to, None, None)
  def apply[T <: Room[T]](from: CollapsedNode[T], to: CollapsedNode[T], originalFrom: Option[T], originalTo: Option[T]) =
    new CollapsedEdge[T]().makeEdge(from, to, originalFrom, originalTo)

  def factory[X <: Room[X]] = new CollapsedEdge[X]().newFactory

  /**
   * Retrieves the represented targets (to/from .1/._2) of a collapsed edge. If the edge targets super-nodes,
   * the original targets are returned instead.
   * @param edge A collapsed edge targeting two collapsed nodes.
   * @tparam T Represented type inside the collapsed nodes.
   * @return The targets represented by the nodes. The first value of the tuple is from/._1, and the second value
   *         is to/._2.
   */
  def targetsOfEdge[T <: Room[T]](edge: CollapsedEdge[T]#Inner[CollapsedNode[T]]): (T, T) = {
    val firstTarget = if (edge.originalFrom.isDefined) edge.originalFrom.get else edge._1.singleRepresentedVertex
    val secondTarget = if (edge.originalTo.isDefined) edge.originalTo.get else edge._2.singleRepresentedVertex
    (firstTarget, secondTarget)
  }

  /**
   * Finds which node on a main path that this edge connects to.
   * @param edge Edge to examine.
   * @tparam T Type of vertex being stored inside a collapsed node.
   * @return A vertex on the main path of a graph if at least one of the nodes inside the edge belongs to a pointless
   *         area (i.e if its PointlessAreaData is set), or None if no such data is present.
   */
  def mainAreaConnectionOfEdge[T <: Room[T]](edge: CollapsedEdge[T]#Inner[CollapsedNode[T]]): Option[T] = {
    val a = edge._1.getMainAreaConnection
    val b = edge._2.getMainAreaConnection

    if (a.isDefined) a else if (b.isDefined) b else None
  }

}
